#include <iostream>
#include <sys/stat.h>
#include <limits.h>
#include <iomanip>
#include <functional>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>

#include "utils.h"


#define CFGO_LOG_PATH "/etc/cfgo/cfgo.log"

static std::map<std::string, log4cplus::LogLevel> log_level = {
    {"DEBUG", log4cplus::DEBUG_LOG_LEVEL},
    {"INFO", log4cplus::INFO_LOG_LEVEL},
    {"WARN", log4cplus::WARN_LOG_LEVEL},
    {"ERROR", log4cplus::ERROR_LOG_LEVEL},
    {"FATAL", log4cplus::FATAL_LOG_LEVEL},
};
global_config *configs = nullptr;

Logger::Logger(std::string name) {
    logger = log4cplus::Logger::getInstance(name);
    if (logger.getLogLevel() != log4cplus::NOT_SET_LOG_LEVEL) {
        // 已经实例化，无需处理
        return;
    }
    log4cplus::SharedAppenderPtr appender(new log4cplus::ConsoleAppender()); 
    appender->setName("console");
    log4cplus::tstring pattern =
        LOG4CPLUS_TEXT("%D{%m/%d/%y %H:%M:%S} [%t] %-5p %c - %m%n");
    appender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::PatternLayout(pattern)));
    logger.setLogLevel(log4cplus::DEBUG_LOG_LEVEL);
    logger.addAppender(appender);
}

void Logger::init() {   
    if (logger.getAppender("file") != nullptr) {
        return;
    }

    log4cplus::SharedAppenderPtr appender(new log4cplus::FileAppender(CFGO_LOG_PATH, std::ios_base::app));
    appender->setName("file");
    log4cplus::tstring pattern =
        LOG4CPLUS_TEXT("%D{%m/%d/%y %H:%M:%S} [%t] %-5p %c - %m%n");
    appender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::PatternLayout(pattern)));
    logger.setLogLevel(log4cplus::DEBUG_LOG_LEVEL);
    logger.addAppender(appender);
}

void cleanup_configs() {
    if (configs == nullptr) {
        return;
    }
    while (configs->app) {
        auto tmp = configs->app->next;
        if (tmp) {
            configs->app->next = configs->app->next->next;
            delete tmp;
        } else {
            delete configs->app;
            configs->app = nullptr;
        }
    }
    delete configs;
    configs = nullptr;
}

int parse_general(boost::property_tree::ptree pt) {

    try {
        auto it = log_level.find(pt.get<std::string>("general.LOG_LEVEL"));
        if (it == log_level.end()) {
            ERROR("Wrong log level: " << pt.get<std::string>("general.LOG_LEVEL"));
            return -1;
        } else {
            configs->log_level = it->second;
        }
        logger.setLogLevel(configs->log_level);
        configs->sampling_strategy         = pt.get<int>("general.COLLECTOR_SAMPLING_STRATEGY");
        configs->high_load_threshold       = pt.get<int>("general.COLLECTOR_HIGH_LOAD_THRESHOLD");
        configs->collector_sampling_period = pt.get<int>("general.COLLECTOR_SAMPLING_PERIOD");
        configs->collector_sampling_freq   = pt.get<int>("general.COLLECTOR_SAMPLING_FREQ");
        configs->collector_dump_data_threshold = pt.get<int>("general.COLLECTOR_DUMP_DATA_THRESHOLD");
        configs->collector_data_aging_time    = pt.get<int>("general.COLLECTOR_DATA_AGING_TIME");
        configs->tuner_tool                = pt.get<std::string>("general.TUNER_TOOL");
        configs->tuner_check_period        = pt.get<int>("general.TUNER_CHECK_PERIOD");
        configs->tuner_profile_dir         = pt.get<std::string>("general.TUNER_PROFILE_DIR");
        configs->tuner_optimizing_strategy = pt.get<int>("general.TUNER_OPTIMIZING_STRATEGY");
        configs->tuner_optimizing_condition = pt.get<int>("general.TUNER_OPTIMIZING_CONDITION");
    } catch (const boost::property_tree::ptree_bad_path &e) {
        ERROR("Error accessing property: " << e.what());
        return -1;
    }

    DEBUG("[CFGO_CONFIG] INI_PATH                     : "
          << configs->ini_path);
    DEBUG("[CFGO_CONFIG] COLLECTOR_SAMPLING_STRATEGY  : "
          << configs->sampling_strategy);
    DEBUG("[CFGO_CONFIG] COLLECTOR_HIGH_LOAD_THRESHOLD: "
          << configs->high_load_threshold);
    DEBUG("[CFGO_CONFIG] COLLECTOR_SAMPLING_PERIOD    : "
          << configs->collector_sampling_period);
    DEBUG("[CFGO_CONFIG] COLLECTOR_SAMPLING_FREQ      : "
          << configs->collector_sampling_freq);
    DEBUG("[CFGO_CONFIG] COLLECTOR_DUMP_DATA_THRESHOLD: "
          << configs->collector_dump_data_threshold);
    DEBUG("[CFGO_CONFIG] COLLECTOR_DATA_AGING_TIME    : "
          << configs->collector_data_aging_time);
    DEBUG("[CFGO_CONFIG] TUNER_TOOL                   : "
          << configs->tuner_tool);
    DEBUG("[CFGO_CONFIG] TUNER_CHECK_PERIOD           : "
          << configs->tuner_check_period);
    DEBUG("[CFGO_CONFIG] TUNER_PROFILE_DIR            : "
          << configs->tuner_profile_dir);
    DEBUG("[CFGO_CONFIG] TUNER_OPTIMIZING_STRATEGY    : "
          << configs->tuner_optimizing_strategy);
    DEBUG("[CFGO_CONFIG] TUNER_OPTIMIZING_CONDITION   : "
          << configs->tuner_optimizing_condition);
    return 0;
}

std::string generate_app_profile_path(app_config *app)
{
    std::stringstream ss;
    uint32_t hash = static_cast<uint32_t>(
        std::hash<std::string>{}(app->full_path) & 0xFFFFFFFF);
    ss << std::hex << std::setw(8) << std::setfill('0') << hash;
    return configs->tuner_profile_dir + "/" +
        app->app_name + "_" + ss.str() + ".profile";
}

int parse_app(boost::property_tree::ptree pt, std::string app_name) {
    DEBUG("[CFGO_CONFIG] APP: " << app_name);
    std::string full_path = "";
    try {
        full_path = pt.get<std::string>(app_name + ".FULL_PATH");
    } catch (const boost::property_tree::ptree_bad_path &e) {
        ERROR("FULL_PATH is needed.");
        return -1;
    }
    boost::filesystem::path filePath(full_path);
    if (!boost::filesystem::exists(filePath)) {
        ERROR("Error: File does not exist: " << filePath);
        return -1;
    }

    auto app = new app_config;
    app->next = configs->app;
    configs->app = app;

    app->full_path    = full_path;
    app->app_name     = app_name;
    app->current_pid  = INVALID_PID;
    app->profile.ts = 0;
    app->profile_path = generate_app_profile_path(app);
    app->status = UNOPTIMIZED;
    app->bolt_options = "";

    try {
        app->default_profile =
            pt.get<std::string>(app_name + ".DEFAULT_PROFILE");
        if (app->default_profile != "") {
            if (!boost::filesystem::exists(app->default_profile)) {
                ERROR("default profile does not exist: " << app->default_profile);
                return -1;
            }
            app->status = NEED_OPTIMIZED;
        }
    } catch (const boost::property_tree::ptree_bad_path &e) {
        DEBUG(app_name << " has no default profile");
    }

    try {
        app->bolt_options =
            pt.get<std::string>(app_name + ".BOLT_OPTIONS");
        // 去掉首尾引号
        auto opts = app->bolt_options;
        if (opts.length() >= 2 && opts.front() == '"' && opts.back() == '"') {
            app->bolt_options = opts.substr(1, opts.length() - 2);
        }
    } catch (const boost::property_tree::ptree_bad_path &e) {
        DEBUG(app_name << " has no customized bolt options");
        ;
    }

    return 0;
}

// 解析ini文件并更新全局配置信息
int parse_cfgo_ini(std::string ini_path) {
    cleanup_configs();

    configs = new global_config;
    boost::property_tree::ptree pt;
    try {
        boost::property_tree::ini_parser::read_ini(ini_path, pt);
    } catch (const boost::property_tree::ini_parser::ini_parser_error &e) {
        ERROR("Error reading " << ini_path);
        return -1;
    }
    configs->ini_path = ini_path;
    if (parse_general(pt) != 0) {
        return -1;
    }
    configs->app = nullptr;
    for (const auto &section : pt) {
        if (section.first != "general" && parse_app(pt, section.first) != 0) {
            return -1;
        }
    }
    return 0;
}

// TODO: 实现bin校验功能
std::string get_bin_build_id(std::string full_path) {
    (void)full_path;
    return "Not implemented";
}

bool get_real_path(const char* path, char* resolved) {
    if (realpath(path, resolved) == nullptr) {
        ERROR("Error resolving path: " << path << " - " << strerror(errno));
        return false;
    }
    return true;
}

// full_path为空时，如果有多个同名进程，返回获取到的第一个pid
// full_path不为空，则获取到pid后需要校验路径
int get_target_pid (app_config *app)
{
    char real1[PATH_MAX], real2[PATH_MAX];
    int pid = -1;
    char buffer[1024] = {0};

    sprintf(buffer, "pidof %s", app->app_name.c_str());

    FILE *fp = popen(buffer, "r");
    if (fp == NULL) {
        ERROR("Error executing command: " << buffer);
        return -1;
    }

    if (fgets(buffer, sizeof(buffer), fp) != NULL) {
        pclose(fp);
        pid = atoi(buffer);
    }

    // 当输出存在指定路径时，判断和查到的pid对应的二进制是否是同一个文件
    // TODO: 判断时需要考虑已经被优化的二进制带有.rto后缀
    // TODO: 保证软件包完整性，可能要生成二进制到其他路径，需要补充二进制校验
    if (app->full_path != "" && pid > 0) {
        struct stat stat1, stat2;
        sprintf(buffer, "/proc/%d/exe", pid);
        if (!get_real_path(app->full_path.c_str(), real1) ||
            !get_real_path(buffer, real2) ||
            stat(real1, &stat1) == -1 || stat(real2, &stat2) == -1 ||
            stat1.st_dev != stat2.st_dev || stat1.st_ino != stat2.st_ino) {
            DEBUG("expected full_path: " << app->full_path); // /path/to/bin
            DEBUG("full_path realpath: " << real1);          // /real/path/to/bin
            DEBUG("actual app path   : " << buffer);         // /proc/[pid]/exe
            DEBUG("app realpath      : " << real2);          // /real/path/to/bin or /real/path/to/bin.rto
            return -1;
        }
    }

    return pid;
}

std::vector<int> update_pid_in_configs() {
    std::vector<int> pids;
    if (configs == nullptr) {
        return pids;
    }

    for (auto app = configs->app; app != nullptr; app = app->next) {
        app->current_pid = get_target_pid(app);
        if (app->current_pid > 0) {
            pids.push_back(app->current_pid);
        }
    }
    return pids;
}

exec_result exec_cmd(std::string cmd)
{
    std::string log;
    int ret;
    DEBUG("exec cmd: \"" << cmd << "\"");
    FILE* pipe = popen(cmd.c_str(), "r");
    if (!pipe) {
        ERROR("popen failed");
        return {"", -1};
    }

    char buffer[256];
    while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        log += buffer;
        DEBUG(buffer);
    }

    ret = pclose(pipe);
    return {log, ret};
}